﻿using CRL.Core;
using CRL.Core.Extension;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CRL.Cache
{
    /// <summary>
    /// 自定义委托和过期时间,实现缓存
    /// </summary>
    public abstract class DelegateCacheBase
    {
        //core 调用 https://cloud.tencent.com/developer/article/1546485
        IDelegateCacheContainer cacheContainer;
        bool ThrowUpdateException = true;
        ConcurrentDictionary<string, CacheObjInfo> localCache = new ConcurrentDictionary<string, CacheObjInfo>();
        ThreadWork work;
        public DelegateCacheBase(IDelegateCacheContainer container)
        {
            cacheContainer = container;
            work = new ThreadWork();
            //return;
            work.Start($"updateDelegateCache {cacheContainer.GetType().Name}", () =>
            {
                foreach (var kv in localCache.Where(b => b.Value.needUpdate || (b.Value.autoUpdate && b.Value.expTime <= DateTime.Now)))
                {
                    var value = kv.Value;
                    try
                    {
                        var sw = new System.Diagnostics.Stopwatch();
                        sw.Start();
                        var data = value.Method.Invoke(value.funcDelegate, value.funcArgs);
                        value.needUpdate = false;
                        value.expTime = DateTime.Now.AddMinutes(value.expMinute);
                        cacheContainer.Write(kv.Key, value.dataType, new cacheDataItem
                        {
                            data = data,
                            expTime = value.expTime,
                            args = value.funcArgs,
                        });
                        sw.Stop();
                        var el = sw.ElapsedMilliseconds;
                        //Console.WriteLine($"{DateTime.Now} {cacheContainer.GetType().Name} updateCache {kv.Key} args {value.funcArgs.ToJson()} {el} ms");
                        kv.Value.lastException = null;
                    }
                    catch (Exception ero)
                    {
                        value.lastException = ero.InnerException ?? ero;
                        Console.WriteLine($"{DateTime.Now} {cacheContainer.GetType().Name} updateCache error {kv.Key} args {value.funcArgs.ToJson()} {ero}");
                        EventLog.Log($"updateCache error {kv.Key} args {value.funcArgs.ToJson()} {ero}", "updateCacheError");
                    }
                }
                return true;
            }, 1);
        }

        public void SetThrowUpdateException(bool v)
        {
            ThrowUpdateException = v;
        }

        /// <summary>
        /// 初始缓存信息
        /// </summary>
        /// <param name="key"></param>
        /// <param name="minute">过期时间,单位分</param>
        /// <param name="handler">委托</param>
        public T Init<T>(string key, double minute, Func<T> handler)
        {
            var a = localCache.TryGetValue(key, out var cacheObjInfo);
            var b = cacheContainer.Get(key, typeof(T), out var cacheData);
            if (!a)
            {
                cacheObjInfo = new CacheObjInfo()
                {
                    expTime = DateTime.Now.AddMinutes(minute),
                    dataType = typeof(T),
                };
                if (b)
                {
                    cacheObjInfo.expTime = cacheData.expTime;
                }
                localCache.TryRemove(key, out var obj2);
                localCache.TryAdd(key, cacheObjInfo);
            }
            //本地过期或数据过期
            if (cacheObjInfo.expTime < DateTime.Now || (b && cacheData.expTime < DateTime.Now))
            {
                cacheObjInfo.expTime = DateTime.Now.AddMinutes(minute);
                cacheData = new cacheDataItem { data = handler(), expTime = cacheObjInfo.expTime };
                cacheContainer.Write(key, cacheObjInfo.dataType, cacheData);
            }
            if (!b && cacheData == null)
            {
                cacheData = new cacheDataItem { data = handler(), expTime = cacheObjInfo.expTime };
                cacheContainer.Write(key, cacheObjInfo.dataType, cacheData);
            }
            return cacheData.GetData<T>(cacheContainer.CacheContainerType);
        }

        public T InitAsyncUpdate<TArgs1, T>(string key, double minute, bool autoUpdate, Func<TArgs1, T> handler, TArgs1 args1)
        {
            return InitAsyncUpdateBase<T>(key, minute, autoUpdate, handler, new object[] { args1 });
        }
        public T InitAsyncUpdate<TArgs1, TArgs2, T>(string key, double minute, bool autoUpdate, Func<TArgs1, TArgs2, T> handler, TArgs1 args1, TArgs2 args2)
        {
            return InitAsyncUpdateBase<T>(key, minute, autoUpdate, handler, new object[] { args1, args2 });
        }
        public T InitAsyncUpdate<TArgs1, TArgs2, TArgs3, T>(string key, double minute, bool autoUpdate, Func<TArgs1, TArgs2, TArgs3, T> handler, TArgs1 args1, TArgs2 args2, TArgs3 args3)
        {
            return InitAsyncUpdateBase<T>(key, minute, autoUpdate, handler, new object[] { args1, args2, args3 });
        }
        public T GetBaseData<T>(string key)
        {
            var b = cacheContainer.Get(key, typeof(T), out var cacheData);
            return cacheData.GetData<T>(cacheContainer.CacheContainerType);
        }
        public T InitAsyncUpdateBase<T>(string key, double minute, bool autoUpdate, Delegate funcDelegate, object[] parame)
        {
            var funcType = funcDelegate.GetType();
            var method = funcType.GetMethod("Invoke");
            var argsType = method.GetParameters();
            var i = 0;
            List<object> args = new List<object>();
            //重新序列化参数，将引用类型改为值类型
            foreach (var t in argsType)
            {
                var v = parame[i];
                i += 1;
                args.Add(SerializeHelper.DeserializeFromJson(v.ToJson(), t.ParameterType));
            }
            var a = localCache.TryGetValue(key, out var cacheObjInfo);
            var b = cacheContainer.Get(key, typeof(T), out var cacheData);
            if (!a)
            {
                cacheObjInfo = new CacheObjInfo()
                {
                    expTime = DateTime.Now.AddMinutes(minute),
                    dataType = typeof(T),
                    funcArgs = args.ToArray(),
                    autoUpdate = autoUpdate,
                    expMinute = minute
                };
                if (b)
                {
                    cacheObjInfo.expTime = cacheData.expTime;
                }
                cacheObjInfo.Method = method;
                cacheObjInfo.funcDelegate = funcDelegate;
                localCache.TryRemove(key, out var obj2);
                localCache.TryAdd(key, cacheObjInfo);
            }
            //本地过期或数据过期
            if (cacheObjInfo.expTime < DateTime.Now || (b && cacheData.expTime < DateTime.Now))
            {
                if (!autoUpdate)
                {
                    //下次请求时返回新的值
                    cacheObjInfo.needUpdate = true;
                }
                if (ThrowUpdateException && cacheObjInfo.lastException != null)
                {
                    throw cacheObjInfo.lastException;
                }
            }
            cacheObjInfo.funcArgs = args.ToArray();
            if (!b && cacheData == null)
            {
                var value2 = method.Invoke(funcDelegate, args.ToArray());
                cacheData = new cacheDataItem { data = value2, expTime = cacheObjInfo.expTime, args = args };
                cacheContainer.Write(key, cacheObjInfo.dataType, cacheData);
            }
            return cacheData.GetData<T>(cacheContainer.CacheContainerType);
        }

        /// <summary>
        /// 移除
        /// </summary>
        /// <param name="key"></param>
        public void Remove(string key)
        {
            var a = localCache.TryGetValue(key, out var cacheObj);
            localCache.TryRemove(key, out var obj2);
            if (a)
            {
                cacheContainer.Remove(key, cacheObj.dataType);
            }
        }
        public List<string> GetAllKeys()
        {
            return cacheContainer.GetAllKeys();
        }

    }
}
